
#include "wave_reader.h"
#include <cstring>

namespace {
    
struct BufferWriter
{
    char* buf_;
    int pos_;
    int size_;

    BufferWriter(): buf_(nullptr), pos_(0), size_(0)
    {
    }

    BufferWriter(char* buf, int size): buf_(buf), pos_(0), size_(size)
    {
    }

    ~BufferWriter() 
    {
    }

    int Write(char* data, int len)
    {
        if(buf_ == nullptr) return 0;

        int nleft = size_ - pos_;
        int nwrite = nleft < len ? nleft : len;

        memcpy(buf_ + pos_, data, nwrite);
        pos_ += nwrite;

        return nwrite;
    }
};

void PrintError(char* info)
{
    fprintf(stderr, "[%s][%d][%s]: %s\n", __FILE__, __LINE__, __FUNCTION__, info);
}

} // namespace name

WaveReader::WaveReader(): file_(nullptr), file_size_(0), pos_(0), sample_rate_(0), channel_count_(0), bit_per_sample_(0)
{
}

WaveReader::~WaveReader()
{
    Close();
}

int WaveReader::Open(const char *filepath)
{
    filepath_ = filepath;

    // open file
    file_ = ::fopen(filepath, "r");
    if(file_ == nullptr) {
        PrintError("open failed");
        return -1;
    }

    fseek(file_, 0, SEEK_END);
    file_size_ = ftell(file_);
    fseek(file_, 0, SEEK_SET);

    if(file_size_ <= 0) {
        PrintError("invalid file size");
        return -1;
    }

    // create file buffer
    audio_data_.reset(new char[file_size_]);

    // read all data
    if(1 != fread(audio_data_.get(), file_size_, 1, file_)) {
        Close();    
        return -1;
    }

    // get audio information
    // wave sample rate info lay at offset 24 as unsigned 4 byte int
    sample_rate_ = *(uint32_t*)(audio_data_.get() + 24);
    // wave channel info lay at offset 22 as unsigned 2 byte int
    channel_count_ = *(uint16_t*)(audio_data_.get() + 22);
    // wave channel info lay at offset 34 as unsigned 2 byte int
    bit_per_sample_ = *(uint16_t*)(audio_data_.get() + 34);
    // data start 44 byte offset
    pos_ = 44;

    return 0;
}

void WaveReader::Close()
{
    if(file_!= nullptr) {
        fclose(file_);
        file_= nullptr; 
    }

    audio_data_.reset();
    filepath_.clear();
    file_size_ = 0; 
    pos_ = 0;
    sample_rate_ = 0; 
    channel_count_ = 0;
    bit_per_sample_ = 0;
}

std::size_t WaveReader::Read(char *data, std::size_t len)
{
    if(filepath_.empty()) {
        PrintError("file don't open");
        return -1;
    }

    if(data == nullptr) {
        return -1;
    }

    if(pos_ >= file_size_) {
        return 0;
    }

    int nleft = file_size_ - pos_;
    int nread = nleft < len ? nleft : len;

    memcpy(data, audio_data_.get() + pos_, nread);
    pos_ += nread;

    return nread;
}

int WaveReader::GetAudioInfo(uint32_t &sample_rate, uint16_t &channel_count, uint16_t &bit_per_sample) const
{
    if(filepath_.empty()) {
        return -1;
    }

    sample_rate = sample_rate_;
    channel_count = channel_count_;
    bit_per_sample = bit_per_sample_;
    return 0;
}

WaveWriter::WaveWriter(): file_(nullptr), file_size_(0), sample_rate_(0), channel_count_(0), bit_per_sample_(0)
{

}

WaveWriter::~WaveWriter()
{
    Reset();
}

int WaveWriter::SetAudioInfo(uint32_t sample_rate, uint16_t channel_count, uint16_t bit_per_sample)
{
    if(sample_rate == 0 || channel_count == 0 || bit_per_sample == 0) {
        PrintError("wave file audio info wasn't setup correctlly.");
        return -1;
    }

    sample_rate_ = sample_rate;
    channel_count_ = channel_count;
    bit_per_sample_ = bit_per_sample;
    return 0;
}

int WaveWriter::Open(const char *filepath)
{

    filepath_ = filepath;
    file_ = fopen(filepath, "w+");
    if(file_ == nullptr) {
        PrintError("open file failed");
        Reset();
        return -1;
    }

    // set the wave header when close the file. now let them to be null
    if(1 != fwrite("\0", 44, 1, file_)) {
        Reset();
        return -1;
    }

    return 0;
}

void WaveWriter::Close()
{
    WriteHeader();
    Reset();
}

int WaveWriter::Write(const char *data, std::size_t len, std::size_t nmeb)
{
    long offset = ftell(file_);

    int ret = 0;
    int nwrite = 0;
    int nleft = 0;
    while(nwrite < nmeb) {
        nleft = nmeb - nwrite;
        ret = fwrite(data, len, nleft, file_);
        if(ret < 0) {
            fseek(file_, offset, SEEK_SET);
            return -1;
        }

        nwrite += ret;
    }

    return nmeb;
}

void WaveWriter::Reset()
{
    if(file_ != nullptr) {
        fclose(file_);
        file_ = nullptr;
    }

    filepath_.clear();
    sample_rate_ = 0;
    channel_count_ = 0;
    bit_per_sample_ = 0;
}


int WaveWriter::WriteHeader()
{
    if(filepath_.empty() || file_ == nullptr) {
        return -1;
    }

    std::shared_ptr<char[]> wave_header;

    int ret = ConstructWaveHeader(wave_header);
    if(ret != 0) {
        return -1;
    }

    long cur_pos = ftell(file_);
    fseek(file_, 0, SEEK_SET);

    fwrite(wave_header.get(), 44, 1, file_);

    fseek(file_, cur_pos, SEEK_SET);

    return 0;
}


int WaveWriter::ConstructWaveHeader(std::shared_ptr<char[]> &wave_header)
{
    BufferWriter bw(new char[44], 44);

    if(sample_rate_ == 0 || channel_count_ == 0 || bit_per_sample_ == 0) {
        PrintError("audio info wasn't setup");
        return -1;
    }

    if(bw.buf_ == nullptr) return -1;

    // file size
    long cur_pos = ftell(file_);
    fseek(file_, 0, SEEK_END);
    long file_size = ftell(file_);
    fseek(file_, cur_pos, SEEK_SET);

    // wave ChundID
    bw.Write("RIFF", 4);
    // wave ChunkSize
    uint32_t chunk_size = file_size - 8;
    bw.Write((char*)&chunk_size, 4);

    // wave format
    bw.Write("WAVE", 4);
    // SubchunkID 
    bw.Write("fmt ", 4);
    // SubchunkSize
    uint32_t sub_chunk_size = 16; 
    bw.Write((char*)&sub_chunk_size, 4);
    // AudioFormat
    uint16_t audio_format = 1;
    bw.Write((char*)&audio_format, 2);
    // NumChannel
    bw.Write((char*)&channel_count_, 2);
    // SampleRate
    bw.Write((char*)&sample_rate_, 4);
    // ByteRate
    uint32_t byte_rate = sample_rate_ * channel_count_ * (bit_per_sample_/8);
    bw.Write((char*)&byte_rate, 4);
    // BlockAlign
    uint16_t block_align = channel_count_ * (bit_per_sample_/8);
    bw.Write((char*)&block_align, 2);
    // BitPerSample
    bw.Write((char*)&bit_per_sample_, 2);
    // Data subchunk
    bw.Write("data", 4);
    // Data Subchunk size
    uint32_t data_chunk_size = file_size - 44;
    bw.Write((char*)&data_chunk_size, 4);

    wave_header.reset(bw.buf_);
    return 0;
}
